package sw6.launcher;

import girafPlace.lib.Application;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.StreamCorruptedException;
import java.util.ArrayList;
import java.util.List;

import sw6.launcher.TableInfo.Applications;
import sw6.lib.Settings;
import sw6.lib.user.Profile;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

/**
 * Adapter that maintains the list of applications for the launcher.
 * The class has been implemented as a singleton, use getInstance(Context) for instantiation.
 * 
 * @author SW6C
 *
 */
public class IconAdapter extends BaseAdapter {
	/** The context, the Adapter exists in. */
    private static Context mContext;
	
	/** Tag for log messages. */
	protected final String tag = "GIRAF";
	
	/** List of the package name of applications. */
	protected List<String> applications;
	
	/** List of application icons. */
	protected List<Drawable> icons;
	
	/** List of icon labels. This label is displayed below the application icon. */
	protected List<String> labels;
	
	/** Amount of applications in the Adapter. */
	protected int size = 0;
	
	/** Class instance - part of the singleton pattern. */
	private static IconAdapter instance = null;
	
	/** Private constructor to avoid instantiation - part of the singleton pattern. */
	private IconAdapter() {
	}
	
	/**
	 * Retrieve the current instance - part of the singleton pattern.
	 * @param context The context of the application
	 * @return The current instance
	 */
	public static IconAdapter getInstance(Context context) {
		mContext = context;
		if(instance == null) {
			instance = new IconAdapter();
			instance.FindApps();
		}
		return instance;
	}
    
    @Override
	public int getCount() {
			return size;
	}
	
	@Override
	public Object getItem(int position) {
		if(applications == null)
			return null;
		else
			return applications.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Log.d(tag, "Requesting id " + position + " When the number of applications is: " + applications.size());
		Icon icon;
        if (convertView == null) {  // if it's not recycled, initialize some attributes
            icon = new Icon(mContext);
            
        } else {
            icon = (Icon) convertView;
        }
        Log.d(tag,String.format("Getting the icon for position %1$d", position));
        icon.setImageDrawable(icons.get(position));
        icon.setText(labels.get(position));
        return icon;
	}

	/**
	 * Creates a new asynchronous task for loading applications. 
	 */
	private void FindApps() {
		new AsyncLoader().execute();
	}

    /**
     * Creates a new asynchronous task for loading applications. 
     */
	public void updateApps() {
		new AsyncLoader().execute();
	}
	
	/**
	 * Class for loading the applications and their icons. 
	 * A progress dialog will be displayed, to show the application is working.
	 * 
	 * @author SW6C
	 *
	 */
	private class AsyncLoader extends AsyncTask<Void,Void,Void> {
		/** The dialog displaying the progress of the download. */
	    ProgressDialog loadingDialog;
		
		@Override
		protected void onPreExecute() {
			loadingDialog = ProgressDialog.show(mContext, "Loading", "Please wait while the applications are being loaded",true,false);
			size = 0;
			notifyDataSetInvalidated();
			super.onPreExecute();
		}
		
		@Override
		protected Void doInBackground(Void... params) {
			applications = buildInfos();
			icons = buildIcons();
			labels = buildLabels();
			
			size = applications.size();
			
			return null;
		}
		
		@Override
		protected void onPostExecute(Void result) {
			try {
				loadingDialog.dismiss();
			} catch (Exception e) {
				Log.e("GIRAF", "Could not dismiss dialog");
			}
			
			notifyDataSetChanged();
			super.onPostExecute(result);
		}
	}
	
	/**
	 * Retrieve application package names that can be opened by the current user at the current location.
	 * @return A list of Application package names that are launchable by the current user.
	 */
	@SuppressWarnings("unchecked")
	private List<String> buildInfos() {
		List<String> SSIDS = getSSIDS();
		List<String> result = new ArrayList<String>();
		
		String[] projection = {Applications._ID, Applications.PACKAGE_NAME,Applications.SSID_BLACKLIST,Applications.APPLICATION_OBJECT};
		
		Cursor cursor = mContext.getContentResolver().query(Uri.parse("content://" + TableInfo.AUTHORITY + "/applications"), projection, null, null, null);
		
		Application app = null;
		List<String> SSIDBlackList = null;
		boolean passed = true;
		
		if(cursor.moveToFirst()) {
			do {
					passed = true;
					//Get the application object from the datastream
				    ByteArrayInputStream bis = new ByteArrayInputStream (cursor.getBlob(cursor.getColumnIndex(Applications.APPLICATION_OBJECT)));
				    ObjectInputStream ois;
					try {
						ois = new ObjectInputStream (bis);
						app = (Application) ois.readObject();
					} catch (StreamCorruptedException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					} catch (ClassNotFoundException e) {
						Log.e(tag,"The application class could not be found");
					}
				    
				    
				    Log.d(tag,"Checking application: " + app.getPackage());
				    passed = testProfile(app);

				    if(!passed)
				    	Log.d(tag,"Userprofile does not allow for this application");
				    
				    else {
				    	try{
				    		ByteArrayInputStream bisSSID = new ByteArrayInputStream (cursor.getBlob(cursor.getColumnIndex(Applications.SSID_BLACKLIST)));
				    		ObjectInputStream oisSSID = new ObjectInputStream (bisSSID);
				    		SSIDBlackList = (ArrayList<String>) oisSSID.readObject();
				    	} catch (ClassNotFoundException e) {
				    		Log.e(tag,"The arraylist class could not be found");
				    	} catch (StreamCorruptedException e) {
				    		Log.e(tag,"The stream for the blacklist was corupted!");
				    	} catch (IOException e) {
				    		Log.e(tag,"The was an IOException: " + e.getMessage());
				    	} 

				    	passed = compareSSIDS(SSIDBlackList,SSIDS);

				    	if(!passed)
					    	Log.d(tag,"SSID blacklist does not allow for this application");
				    	else
				    		result.add(cursor.getString(cursor.getColumnIndex(Applications.PACKAGE_NAME)));
				    }
				
			}while(cursor.moveToNext());
		}
		cursor.close();
		return result;
	}
	
	/**
	 * Whether the current profile accepts the application given.
	 * @param app Application to be tested.
	 * @return True if usable by the user, false if not.
	 */
	private boolean testProfile(Application app) {
	    Profile profile = Settings.getUserProfile(mContext);
	    boolean passed = true;
		
		if (!profile.canDragAndDrop() && app.isCanDragAndDrop()) {
			passed = false;
		}
		
		if (!profile.canHear() && app.isCanHear()) {
			passed = false;
		}
		
		if (!profile.requreSimpleVisualEffects() && app.isRequiresSimpleVisualEffects()) {
			passed = false;
		}
		
		if (!profile.canAnalogTime() && app.isCanAnalogTime()) {
			passed = false;
		}
		
		if (!profile.canDigitalTime() && app.isCanDigitalTime()) {
			passed = false;
		}
		
		if (!profile.canRead() && app.isCanRead()) {
			passed = false;
		}
		
		if (!profile.hasBadVision() && app.isHasBadVision()) {
			passed = false;
		}
		
		if (!profile.requresLargeButtons() && app.isRequiresLargeButtons()) {
			passed = false;
		}
		
		if (!profile.canSpeak() && app.isCanSpeak()) {
			passed = false;
		}
		
		if (!profile.canNumbers() && app.isCanNumbers()) {
			passed = false;
		}
		
		if (!profile.canUseKeyboard() && app.isCanUseKeyboard()) {
			passed = false;
		}
		return passed;
	}
	
	/**
	 * Compares two lists of SSIDS. Will return false if a string is on both lists
	 * @param sSIDBlackList The first list
	 * @param sSIDS The second list
	 * @return False if a string is on both lists, otherwise true.
	 */
	private boolean compareSSIDS(List<String> sSIDBlackList, List<String> sSIDS) {
		for(String ssidBlack : sSIDBlackList) {
			for(String ssid : sSIDS )
				if(ssid.compareTo(ssidBlack) == 0) return false;
		}
		return true;
	}
	
	/**
	 * Retrieve available SSIDs. If none found, AAU-1x is returned (for development purposes only!)
	 * @return A list of the available SSIDs
	 */
	private List<String> getSSIDS() {
		List<String> result = new ArrayList<String>();
		
		WifiManager wm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
		
		wm.setWifiEnabled(true);
		wm.startScan();
		List<ScanResult> scan = wm.getScanResults();
		if(scan != null){
			
			for(ScanResult sr : scan) {
				result.add(sr.SSID);
			}
		} else {
			result.add("AAU-1x"); //Should only happen on the emulator, and other units without wifi
		}
		
		return result;
	}
	
	/**
	 * Retrieves application labels from the package managers list of applications.
	 * @return List of labels for the applications.
	 */
	private List<String> buildLabels() {
		List<String> result = new ArrayList<String>();
		
		for(String info: applications) {
			try {
				result.add((String) mContext.getPackageManager().getPackageInfo(info, 0).applicationInfo.loadLabel(mContext.getPackageManager()));
			} catch (NameNotFoundException e) {
				Log.e(tag,e.getMessage());
			}
		}
		return result;
	}
	
	/** 
	 * Loads all icons from the application package name in the applications list.
	 * @return List of icons. 
	 */
	private List<Drawable> buildIcons() {
		List<Drawable> result = new ArrayList<Drawable>();
		
		for(String info: applications) {
			try {
				result.add(mContext.getPackageManager().getPackageInfo(info, 0).applicationInfo.loadIcon(mContext.getPackageManager()));
			} catch (NameNotFoundException e) {
				Log.e(tag,e.getMessage());
			}
		}
		
		return result;
	}
}
	